#ifndef JASYNCHRONIZATION_H
#define JASYNCHRONIZATION_H

#include <QTECore/qteglobal.h>
#include <QCoreApplication>
#include <QFutureInterface>
#include <QThreadPool>
#include <QRunnable>
#include <QPointer>
#include <QFuture>
#include <QObject>

namespace QTE
{

#if __cplusplus >= 202002L

namespace JAsyncConcept
{

template <typename Func>
concept F = requires(Func f) {
	f();
};

template <typename Func, typename CallBack>
concept CBHV = requires(Func f, CallBack cb) {
	cb(f());
};

template <typename Func, typename CallBack>
concept FCNV = F<Func> and F<CallBack>;

template <typename Func, typename CallBack>
concept FC = FCNV<Func, CallBack> or CBHV<Func, CallBack>;

template <typename CClass, typename CallBack>
concept CCBNV = requires(CClass *obj, CallBack cb) {
	requires
	requires { (obj->*cb)(); } or
	requires { requires isBaseOfQObject<CClass>::value; cb(); };
};

template <typename Func, typename CClass, typename CallBack>
concept CCBHV = requires(Func f, CClass *obj, CallBack cb) {
	requires
	requires { (obj->*cb)(f()); } or
	requires { requires isBaseOfQObject<CClass>::value; cb(f()); };
};

template <typename Func, class CClass, typename CallBack>
concept FCCNV = F<Func> and CCBNV<CClass, CallBack>;

template <typename Func, class CClass, typename CallBack>
concept FCC = FCCNV<Func, CClass, CallBack> or CCBHV<Func, CClass, CallBack>;

} //namespace JAsyncConcept

# define JASYNC_CONCEPT(C, ...)   requires JAsyncConcept::C <__VA_ARGS__> inline auto

# define JASYNC_CONCEPT_F      JASYNC_CONCEPT(F, Func)
# define JASYNC_CONCEPT_FC     JASYNC_CONCEPT(FC, Func, CallBack)
# define JASYNC_CONCEPT_FCNV   JASYNC_CONCEPT(FCNV, Func, CallBack)
# define JASYNC_CONCEPT_FCC    JASYNC_CONCEPT(FCC, Func, CClass, CallBack)
# define JASYNC_CONCEPT_FCCNV  JASYNC_CONCEPT(FCCNV, Func, CClass, CallBack)

#else // < c++20

namespace JAsyncConcept
{

template <typename Func>
auto __requires(int) -> decltype(std::declval<Func>()(), std::true_type());

template <typename Func, typename CallBack>
auto __requires(int) -> decltype(std::declval<CallBack>()(std::declval<Func>()()), std::true_type());

template <typename Func, typename CallBack>
auto __requires(int) -> decltype(std::declval<Func>()(), std::declval<CallBack>()(), std::true_type());

template <typename Func, class Class, typename CallBack>
auto __requires(int) -> decltype(std::declval<Func>()(), (std::declval<Class>().*std::declval<CallBack>())(), std::true_type());

template <typename Func, class Class, typename CallBack>
auto __requires(int) -> decltype((std::declval<Class>().*std::declval<CallBack>())(std::declval<Func>()()), std::true_type());

template <typename Func, class Class, typename CallBack>
typename std::enable_if<
	isBaseOfQObject<Class>::value,
	decltype(std::declval<Func>()(), std::declval<CallBack>()(), std::true_type())
>::type __requires(int);

template <typename Func, class Class, typename CallBack>
typename std::enable_if<
	isBaseOfQObject<Class>::value,
	decltype(std::declval<CallBack>()(std::declval<Func>()()), std::true_type())
>::type __requires(int);

template <typename> std::false_type __requires(...);
template <typename, typename> std::false_type __requires(...);
template <typename, class, typename> std::false_type __requires(...);

} //namespace JAsyncConcept

# define JASYNC_CONCEPT(cond) \
	inline typename std::enable_if< \
		cond, \
		QFuture<decltype(std::declval<Func>()())> \
	>::type

# define JASYNC_CAN_CALL_VAL1  decltype(JAsyncConcept::__requires<Func>(0))::value
# define JASYNC_CAN_CALL_VAL2  decltype(JAsyncConcept::__requires<Func,CallBack>(0))::value
# define JASYNC_CAN_CALL_VAL3  decltype(JAsyncConcept::__requires<Func,CClass,CallBack>(0))::value

# define JASYNC_CONCEPT_F      JASYNC_CONCEPT(JASYNC_CAN_CALL_VAL1)
# define JASYNC_CONCEPT_FC     JASYNC_CONCEPT(JFunctionTraits<CallBack>::arity == 1 and JASYNC_CAN_CALL_VAL2)
# define JASYNC_CONCEPT_FCNV   JASYNC_CONCEPT(JFunctionTraits<CallBack>::arity == 0 and JASYNC_CAN_CALL_VAL2)
# define JASYNC_CONCEPT_FCC    JASYNC_CONCEPT(JFunctionTraits<CallBack>::arity == 1 and JASYNC_CAN_CALL_VAL3)
# define JASYNC_CONCEPT_FCCNV  JASYNC_CONCEPT(JFunctionTraits<CallBack>::arity == 0 and JASYNC_CAN_CALL_VAL3)

#endif // is c++20

class JAsynchronizationPrivate;

// 异步执行任务（线程池并发）
class QTECORE_EXPORT JAsynchronization : public QObject
{
	Q_OBJECT
	Q_DISABLE_COPY(JAsynchronization)

public:
	explicit JAsynchronization(QObject *parent = nullptr);
	explicit JAsynchronization(bool isCallBackInEventLoop, QObject *parent = nullptr);
	explicit JAsynchronization(QThreadPool *pool, QObject *parent = nullptr);
	JAsynchronization(QThreadPool *pool, bool isCallBackInEventLoop, QObject *parent = nullptr);

public:
	~JAsynchronization();
	static JAsynchronization &globalInstance();
	bool waitForDone(int msecs = -1);

public:
	template <typename Func>
	JASYNC_CONCEPT_F run(Func &&task) const;

	template <typename Func, typename CallBack>
	JASYNC_CONCEPT_FC run(Func &&task, CallBack &&callBack) const;

	template <typename Func, typename CallBack>
	JASYNC_CONCEPT_FCNV run(Func &&task, CallBack &&callBack) const;

	template <typename Func, class CClass, typename CallBack>
	JASYNC_CONCEPT_FCC run(Func &&task, CClass *cobj, CallBack &&callBack) const;

	template <typename Func, class CClass, typename CallBack>
	JASYNC_CONCEPT_FCCNV run(Func &&task, CClass *cobj, CallBack &&callBack) const;

public:
	template <typename Func>
	JASYNC_CONCEPT_F operator()(Func &&task) const;

	template <typename Func, typename CallBack>
	JASYNC_CONCEPT_FC operator()(Func &&task, CallBack &&callBack) const;

	template <typename Func, typename CallBack>
	JASYNC_CONCEPT_FCNV operator()(Func &&task, CallBack &&callBack) const;

	template <typename Func, class CClass, typename CallBack>
	JASYNC_CONCEPT_FCC operator()(Func &&task, CClass *cobj, CallBack &&callBack) const;

	template <typename Func, class CClass, typename CallBack>
	JASYNC_CONCEPT_FCCNV operator()(Func &&task, CClass *cobj, CallBack &&callBack) const;

public:
	void setCallBackInEventLoop(bool flag = true);
	bool isCallBackInEventLoop() const;

public:
	void setThreadPool(QThreadPool *pool);
	QThreadPool *threadPool() const;

private:
	JAsynchronizationPrivate *d_ptr;
};

/* Example:

	void task() {}
	void callBack();

	class A // : public QObject
	{
	public:
		void task()
		void callBack();
	};

	auto async = new JAsynchronization();

	async->run(task);
	async->run(task, callBack);

	A *a = A();

	async->run(std::bind(task, a));
	async->run(std::bind(task, a), callBack);

	async->run(std::bind(task, a), a, callBack);
	async->run(std::bind(task, a), a, &A::callBack);

	async->run(task, a, callBack);
	async->run(task, a, &A::callBack);

*/

class Q_DECL_HIDDEN JRunnableCallBackEvent : public QEvent
{
public:
	inline JRunnableCallBackEvent(std::function<void()> &&callBack, QPointer<const QObject> *qobj) :
		QEvent(User), callBack(callBack), qobj(qobj) {}

public:
	std::function<void()> callBack;
	QPointer<const QObject> *qobj;
};

class QTECORE_EXPORT TaskCallBack : public QObject
{
protected:
	void customEvent(QEvent *event) override;
};

template <typename T>
class Q_DECL_HIDDEN JBasicRunnable :
		protected QFutureInterface<T>, protected QRunnable
{
protected:
	JBasicRunnable(std::function<T()> &&task) :
		m_task(std::move(task)) {}

	template <class CClass>
	JBasicRunnable(std::function<T()> &&task, CClass *cobj) :
		m_task(std::move(task)), m_qobj(makeQPointer(cobj)) {}

public:
	QFuture<T> start(const JAsynchronization *q_ptr)
	{
		m_TaskCallBack = new TaskCallBack();
		m_TaskCallBack->moveToThread(q_ptr->thread());
		m_isCallBackInEventLoop = q_ptr->isCallBackInEventLoop();

		this->setRunnable(this);
		this->reportStarted();

		auto future = this->future();
		q_ptr->threadPool()->start(this);
		return future;
	}

protected:
	void run() override
	{
		task();
		this->reportFinished();
		callBack();
	}

protected:
	virtual void task() = 0;
	virtual void callBack() = 0;

private:
	template <class Class> inline
	typename std::enable_if<not isBaseOfQObject<Class>::value,
	QPointer<const QObject>>::type *makeQPointer(Class*) const { return nullptr; }

	inline QPointer<const QObject> *makeQPointer(const QObject *obj) const
	{ return new QPointer<const QObject>(obj); }

protected:
	bool m_isCallBackInEventLoop;
	std::function<T()> m_task;
	TaskCallBack *m_TaskCallBack;
	QPointer<const QObject> *m_qobj = nullptr;
};

template <typename CRT, typename TR>
class Q_DECL_HIDDEN JAsyncRunnable : public JBasicRunnable<TR>
{
public:
	explicit JAsyncRunnable(std::function<TR()> &&task) :
		JBasicRunnable<TR>(std::move(task)) {}

	template <typename CallBack>
	JAsyncRunnable(std::function<TR()> &&task, CallBack &&callBack) :
		JBasicRunnable<TR>(std::move(task)), m_callBack(std::forward<CallBack>(callBack)) {}

	template <class CClass, typename CallBack>
	JAsyncRunnable(std::function<TR()> &&task, CClass *cobj, CallBack &&callBack) :
		JBasicRunnable<TR>(std::move(task), cobj) { this->bind(cobj, std::forward<CallBack>(callBack)); }

protected:
	virtual void task() { this->reportResult(this->m_task()); }

protected:
	virtual void callBack()
	{
		if( this->m_callBack == nullptr )
			return ;

		else if( this->m_isCallBackInEventLoop )
		{
			auto event = new JRunnableCallBackEvent(std::bind(std::move(this->m_callBack), this->future().result()), this->m_qobj);
			return QCoreApplication::postEvent(this->m_TaskCallBack, event);
		}

		else if( this->m_qobj )
		{
			if( not this->m_qobj->isNull() )
				this->m_callBack(this->future().result());
			delete this->m_qobj;
		}
		else
			this->m_callBack(this->future().result());
	}

private:
	template <class CClass, typename CallBack>
	auto bind(CClass*, CallBack &&func) -> decltype(CRT(func(std::declval<TR>())), true)
	{
		Q_STATIC_ASSERT_X((isBaseOfQObject<CClass>::value),
						  "*** JAsynchronization::run<Func, CClass, CallBack>: CClass is not QObject: CallBack is not a method of CClass.");
		m_callBack = std::forward<CallBack>(func);
		return true;
	}

	template <class CClass, typename CallBack>
	auto bind(CClass *cobj, CallBack &&func) -> decltype(CRT((cobj->*func)(std::declval<TR>())), true)
	{ m_callBack = std::bind(std::forward<CallBack>(func), cobj, std::placeholders::_1); return true; }

private:
	std::function<CRT(TR)> m_callBack = nullptr;
};

template <typename CRT>
class Q_DECL_HIDDEN JAsyncRunnable<CRT,void> : public JBasicRunnable<void>
{
public:
	explicit JAsyncRunnable(std::function<void()> &&task) :
		JBasicRunnable<void>(std::move(task)) {}

	template <typename CallBack>
	JAsyncRunnable(std::function<void()> &&task, CallBack &&callBack) :
		JBasicRunnable<void>(std::move(task)), m_callBack(std::forward<CallBack>(callBack)) {}

	template <class CClass, typename CallBack>
	JAsyncRunnable(std::function<void()> &&task, CClass *cobj, CallBack &&callBack) :
		JBasicRunnable<void>(std::move(task), cobj) { bind(cobj, std::forward<CallBack>(callBack)); }

protected:
	virtual void task()
	{
		this->m_task();
	}

protected:
	virtual void callBack()
	{
		if( m_callBack == nullptr )
			return ;

		else if( m_isCallBackInEventLoop )
		{
			auto event = new JRunnableCallBackEvent(std::move(m_callBack), m_qobj);
			return QCoreApplication::postEvent(m_TaskCallBack, event);
		}

		else if( m_qobj )
		{
			if( not m_qobj->isNull() )
				m_callBack();
			delete m_qobj;
		}
		else
			m_callBack();
	}

private:
	template <class CClass, typename CallBack>
	auto bind(CClass*, CallBack &&func) -> decltype(CRT(func()), true)
	{
		Q_STATIC_ASSERT_X((isBaseOfQObject<CClass>::value),
						  "*** JAsynchronization::run<Func, CClass, CallBack>: CClass is not QObject: CallBack is not a method of CClass.");
		m_callBack = std::forward<CallBack>(func);
		return true;
	}

	template <class CClass, typename CallBack>
	auto bind(CClass *cobj, CallBack &&func) -> decltype(CRT((cobj->*func)()), true)
	{ m_callBack = std::bind(std::forward<CallBack>(func), cobj); return true; }

private:
	std::function<CRT()> m_callBack = nullptr;
};

template <typename Func>
JASYNC_CONCEPT_F JAsynchronization::run(Func &&task) const
{
	return (new JAsyncRunnable<void, decltype(task())>(
				std::function<decltype(task())()>(std::forward<Func>(task))
				))->start(this);
}

template <typename Func, typename CallBack>
JASYNC_CONCEPT_FC JAsynchronization::run(Func &&task, CallBack &&callBack) const
{
	return (new JAsyncRunnable<typename JFunctionTraits<CallBack>::ResultType, decltype(task())>(
				std::function<decltype(task())()>(std::forward<Func>(task)),
				std::forward<CallBack>(callBack)))->start(this);
}

template <typename Func, typename CallBack>
JASYNC_CONCEPT_FCNV JAsynchronization::run(Func &&task, CallBack &&callBack) const
{
	return (new JAsyncRunnable<typename JFunctionTraits<CallBack>::ResultType, void>(
				std::function<decltype(task())()>(std::forward<Func>(task)),
				std::forward<CallBack>(callBack)))->start(this);
}

template <typename Func, class CClass, typename CallBack>
JASYNC_CONCEPT_FCC JAsynchronization::run(Func &&task, CClass *cobj, CallBack &&callBack) const
{
	return (new JAsyncRunnable<typename JFunctionTraits<CallBack>::ResultType, decltype(task())>(
				std::function<decltype(task())()>(std::forward<Func>(task)),
				cobj, std::forward<CallBack>(callBack)))->start(this);
}

template <typename Func, class CClass, typename CallBack>
JASYNC_CONCEPT_FCCNV JAsynchronization::run(Func &&task, CClass *cobj, CallBack &&callBack) const
{
	return (new JAsyncRunnable<typename JFunctionTraits<CallBack>::ResultType, void>(
				std::function<decltype(task())()>(std::forward<Func>(task)),
				cobj, std::forward<CallBack>(callBack)))->start(this);
}

template <typename Func>
JASYNC_CONCEPT_F JAsynchronization::operator()(Func &&task) const
{
	return run(std::forward<Func>(task));
}

template <typename Func, typename CallBack>
JASYNC_CONCEPT_FC JAsynchronization::operator()(Func &&task, CallBack &&callBack) const
{
	return run(std::forward<Func>(task), std::forward<CallBack>(callBack));
}

template <typename Func, typename CallBack>
JASYNC_CONCEPT_FCNV JAsynchronization::operator()(Func &&task, CallBack &&callBack) const
{
	return run(std::forward<Func>(task), std::forward<CallBack>(callBack));
}

template <typename Func, class CClass, typename CallBack>
JASYNC_CONCEPT_FCC JAsynchronization::operator()(Func &&task, CClass *cobj, CallBack &&callBack) const
{
	return run(std::forward<Func>(task), cobj, std::forward<CallBack>(callBack));
}

template <typename Func, class CClass, typename CallBack>
JASYNC_CONCEPT_FCCNV JAsynchronization::operator()(Func &&task, CClass *cobj, CallBack &&callBack) const
{
	return run(std::forward<Func>(task), cobj, std::forward<CallBack>(callBack));
}

} //namespace QTE


#endif //JASYNCHRONIZATION_H
